﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Text.RegularExpressions;

namespace ZX.Common
{
    /// <summary>
    /// 中国日历信息实体类
    /// cncxz（虫虫） 2007-2-9
    /// </summary>
    public sealed class ChineseCalendarInfo
    {
        private DateTime m_SolarDate;
        private int m_LunarYear, m_LunarMonth, m_LunarDay;
        private bool m_IsLeapMonth = false;
        private string m_LunarYearSexagenary = null, m_LunarYearAnimal = null;
        private string m_LunarYearText = null, m_LunarMonthText = null, m_LunarDayText = null;
        private string m_SolarWeekText = null, m_SolarConstellation = null, m_SolarBirthStone = null;
        #region 构造函数

        public ChineseCalendarInfo()
            : this(DateTime.Now.Date)
        {

        }

        /// <summary>
        /// 从指定的阳历日期创建中国日历信息实体类
        /// </summary>
        /// <param name="date">指定的阳历日期</param>
        public ChineseCalendarInfo(DateTime date)
        {
            m_SolarDate = date;
            LoadFromSolarDate();
        }

        private void LoadFromSolarDate()
        {
            m_IsLeapMonth = false;
            m_LunarYearSexagenary = null;
            m_LunarYearAnimal = null;
            m_LunarYearText = null;
            m_LunarMonthText = null;
            m_LunarDayText = null;
            m_SolarWeekText = null;
            m_SolarConstellation = null;
            m_SolarBirthStone = null;

            m_LunarYear = calendar.GetYear(m_SolarDate);
            m_LunarMonth = calendar.GetMonth(m_SolarDate);
            int leapMonth = calendar.GetLeapMonth(m_LunarYear);

            if (leapMonth == m_LunarMonth)
            {
                m_IsLeapMonth = true;
                m_LunarMonth -= 1;
            }
            else if (leapMonth > 0 && leapMonth < m_LunarMonth)
            {
                m_LunarMonth -= 1;
            }

            m_LunarDay = calendar.GetDayOfMonth(m_SolarDate);

            CalcConstellation(m_SolarDate, out m_SolarConstellation, out m_SolarBirthStone);
        }

        #endregion
        #region 日历属性

        /// <summary>
        /// 阳历日期
        /// </summary>
        public DateTime SolarDate
        {
            get { return m_SolarDate; }
            set
            {
                if (m_SolarDate.Equals(value))
                    return;
                m_SolarDate = value;
                LoadFromSolarDate();
            }
        }
        /// <summary>
        /// 星期几
        /// </summary>
        public string SolarWeekText
        {
            get
            {
                if (string.IsNullOrEmpty(m_SolarWeekText))
                {
                    int i = (int)m_SolarDate.DayOfWeek;
                    m_SolarWeekText = ChineseWeekName[i];
                }
                return m_SolarWeekText;
            }
        }
        /// <summary>
        /// 阳历星座
        /// </summary>
        public string SolarConstellation
        {
            get { return m_SolarConstellation; }
        }
        /// <summary>
        /// 阳历诞生石
        /// </summary>
        public string SolarBirthStone
        {
            get { return m_SolarBirthStone; }
        }

        /// <summary>
        /// 阴历年份
        /// </summary>
        public int LunarYear
        {
            get { return m_LunarYear; }
        }
        /// <summary>
        /// 阴历月份
        /// </summary>
        public int LunarMonth
        {
            get { return m_LunarMonth; }
        }
        /// <summary>
        /// 是否阴历闰月
        /// </summary>
        public bool IsLeapMonth
        {
            get { return m_IsLeapMonth; }
        }
        /// <summary>
        /// 阴历月中日期
        /// </summary>
        public int LunarDay
        {
            get { return m_LunarDay; }
        }

        /// <summary>
        /// 阴历年干支
        /// </summary>
        public string LunarYearSexagenary
        {
            get
            {
                if (string.IsNullOrEmpty(m_LunarYearSexagenary))
                {
                    int y = calendar.GetSexagenaryYear(this.SolarDate);
                    m_LunarYearSexagenary = CelestialStem.Substring((y - 1) % 10, 1) + TerrestrialBranch.Substring((y - 1) % 12, 1);
                }
                return m_LunarYearSexagenary;
            }
        }
        /// <summary>
        /// 阴历年生肖
        /// </summary>
        public string LunarYearAnimal
        {
            get
            {
                if (string.IsNullOrEmpty(m_LunarYearAnimal))
                {
                    int y = calendar.GetSexagenaryYear(this.SolarDate);
                    m_LunarYearAnimal = Animals.Substring((y - 1) % 12, 1);
                }
                return m_LunarYearAnimal;
            }
        }

        /// <summary>
        /// 阴历年文本
        /// </summary>
        public string LunarYearText
        {
            get
            {
                if (string.IsNullOrEmpty(m_LunarYearText))
                {
                    m_LunarYearText = Animals.Substring(calendar.GetSexagenaryYear(new DateTime(m_LunarYear, 1, 1)) % 12 - 1, 1);
                    StringBuilder sb = new StringBuilder();
                    int year = this.LunarYear;
                    int d;
                    do
                    {
                        d = year % 10;
                        sb.Insert(0, ChineseNumber[d]);
                        year = year / 10;
                    } while (year > 0);
                    m_LunarYearText = sb.ToString();
                }
                return m_LunarYearText;
            }
        }
        /// <summary>
        /// 阴历月文本
        /// </summary>
        public string LunarMonthText
        {
            get
            {
                if (string.IsNullOrEmpty(m_LunarMonthText))
                {
                    m_LunarMonthText = (this.IsLeapMonth ? "闰" : "") + ChineseMonthName[this.LunarMonth - 1];
                }
                return m_LunarMonthText;
            }
        }

        /// <summary>
        /// 阴历月中日期文本
        /// </summary>
        public string LunarDayText
        {
            get
            {
                if (string.IsNullOrEmpty(m_LunarDayText))
                    m_LunarDayText = ChineseDayName[this.LunarDay - 1];
                return m_LunarDayText;
            }
        }

        #endregion

        /// <summary>
        /// 根据指定阳历日期计算星座＆诞生石
        /// </summary>
        /// <param name="date">指定阳历日期</param>
        /// <param name="constellation">星座</param>
        /// <param name="birthstone">诞生石</param>
        public static void CalcConstellation(DateTime date, out string constellation, out string birthstone)
        {
            int i = Convert.ToInt32(date.ToString("MMdd"));
            int j;
            if (i >= 321 && i <= 419)
                j = 0;
            else if (i >= 420 && i <= 520)
                j = 1;
            else if (i >= 521 && i <= 621)
                j = 2;
            else if (i >= 622 && i <= 722)
                j = 3;
            else if (i >= 723 && i <= 822)
                j = 4;
            else if (i >= 823 && i <= 922)
                j = 5;
            else if (i >= 923 && i <= 1023)
                j = 6;
            else if (i >= 1024 && i <= 1121)
                j = 7;
            else if (i >= 1122 && i <= 1221)
                j = 8;
            else if (i >= 1222 || i <= 119)
                j = 9;
            else if (i >= 120 && i <= 218)
                j = 10;
            else if (i >= 219 && i <= 320)
                j = 11;
            else
            {
                constellation = "未知星座";
                birthstone = "未知诞生石";
                return;
            }
            constellation = Constellations[j];
            birthstone = BirthStones[j];
            #region 星座划分
            //白羊座：   3月21日------4月19日     诞生石：   钻石   
            //金牛座：   4月20日------5月20日   诞生石：   蓝宝石   
            //双子座：   5月21日------6月21日     诞生石：   玛瑙   
            //巨蟹座：   6月22日------7月22日   诞生石：   珍珠   
            //狮子座：   7月23日------8月22日   诞生石：   红宝石   
            //处女座：   8月23日------9月22日   诞生石：   红条纹玛瑙   
            //天秤座：   9月23日------10月23日     诞生石：   蓝宝石   
            //天蝎座：   10月24日-----11月21日     诞生石：   猫眼石   
            //射手座：   11月22日-----12月21日   诞生石：   黄宝石   
            //摩羯座：   12月22日-----1月19日   诞生石：   土耳其玉   
            //水瓶座：   1月20日-----2月18日   诞生石：   紫水晶   
            //双鱼座：   2月19日------3月20日   诞生石：   月长石，血石  
            #endregion
        }

        #region 阴历转阳历

        /// <summary>
        /// 获取指定年份春节当日（正月初一）的阳历日期
        /// </summary>
        /// <param name="year">指定的年份</param>
        private static DateTime GetLunarNewYearDate(int year)
        {
            DateTime dt = new DateTime(year, 1, 1);
            int cnYear = calendar.GetYear(dt);
            int cnMonth = calendar.GetMonth(dt);

            int num1 = 0;
            int num2 = calendar.IsLeapYear(cnYear) ? 13 : 12;

            while (num2 >= cnMonth)
            {
                num1 += calendar.GetDaysInMonth(cnYear, num2--);
            }

            num1 = num1 - calendar.GetDayOfMonth(dt) + 1;
            return dt.AddDays(num1);
        }

        /// <summary>
        /// 阴历转阳历
        /// </summary>
        /// <param name="year">阴历年</param>
        /// <param name="month">阴历月</param>
        /// <param name="day">阴历日</param>
        /// <param name="IsLeapMonth">是否闰月</param>
        public static DateTime GetDateFromLunarDate(int year, int month, int day, bool IsLeapMonth)
        {
            if (year < 1902 || year > 2100)
                throw new Exception("只支持1902～2100期间的农历年");
            if (month < 1 || month > 12)
                throw new Exception("表示月份的数字必须在1～12之间");

            if (day < 1 || day > calendar.GetDaysInMonth(year, month))
                throw new Exception("农历日期输入有误");

            int num1 = 0, num2 = 0;
            int leapMonth = calendar.GetLeapMonth(year);

            if (((leapMonth == month + 1) && IsLeapMonth) || (leapMonth > 0 && leapMonth <= month))
                num2 = month;
            else
                num2 = month - 1;

            while (num2 > 0)
            {
                num1 += calendar.GetDaysInMonth(year, num2--);
            }

            DateTime dt = GetLunarNewYearDate(year);
            return dt.AddDays(num1 + day - 1);
        }

        /// <summary>
        /// 阴历转阳历
        /// </summary>
        /// <param name="date">阴历日期</param>
        /// <param name="IsLeapMonth">是否闰月</param>
        public static DateTime GetDateFromLunarDate(DateTime date, bool IsLeapMonth)
        {
            return GetDateFromLunarDate(date.Year, date.Month, date.Day, IsLeapMonth);
        }

        #endregion
        #region 从阴历创建日历

        /// <summary>
        /// 从阴历创建日历实体
        /// </summary>
        /// <param name="year">阴历年</param>
        /// <param name="month">阴历月</param>
        /// <param name="day">阴历日</param>
        /// <param name="IsLeapMonth">是否闰月</param>
        public static ChineseCalendarInfo FromLunarDate(int year, int month, int day, bool IsLeapMonth)
        {
            DateTime dt = GetDateFromLunarDate(year, month, day, IsLeapMonth);
            return new ChineseCalendarInfo(dt);
        }
        /// <summary>
        /// 从阴历创建日历实体
        /// </summary>
        /// <param name="date">阴历日期</param>
        /// <param name="IsLeapMonth">是否闰月</param>
        public static ChineseCalendarInfo FromLunarDate(DateTime date, bool IsLeapMonth)
        {
            return FromLunarDate(date.Year, date.Month, date.Day, IsLeapMonth);
        }

        /// <summary>
        /// 从阴历创建日历实体
        /// </summary>
        /// <param name="date">表示阴历日期的8位数字，例如：20070209</param>
        /// <param name="IsLeapMonth">是否闰月</param>
        public static ChineseCalendarInfo FromLunarDate(string date, bool IsLeapMonth)
        {
            Regex rg = new System.Text.RegularExpressions.Regex(@"^/d{7}(/d)$");
            Match mc = rg.Match(date);
            if (!mc.Success)
            {
                throw new Exception("日期字符串输入有误！");
            }
            DateTime dt = DateTime.Parse(string.Format("{0}-{1}-{2}", date.Substring(0, 4), date.Substring(4, 2), date.Substring(6, 2)));
            return FromLunarDate(dt, IsLeapMonth);
        }

        #endregion

        private static ChineseLunisolarCalendar calendar = new ChineseLunisolarCalendar();
        public const string ChineseNumber = "〇一二三四五六七八九";
        public const string CelestialStem = "甲乙丙丁戊己庚辛壬癸";
        public const string TerrestrialBranch = "子丑寅卯辰巳午未申酉戌亥";
        public const string Animals = "鼠牛虎兔龙蛇马羊猴鸡狗猪";
        public static readonly string[] ChineseWeekName
            = new string[] { "星期天", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" };
        public static readonly string[] ChineseDayName = new string[] {
"初一","初二","初三","初四","初五","初六","初七","初八","初九","初十",
"十一","十二","十三","十四","十五","十六","十七","十八","十九","二十",
"廿一","廿二","廿三","廿四","廿五","廿六","廿七","廿八","廿九","三十"};
        public static readonly string[] ChineseMonthName
            = new string[] { "正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二" };
        public static readonly string[] Constellations
            = new string[] { "白羊座", "金牛座", "双子座", "巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座", "水瓶座", "双鱼座" };
        public static readonly string[] BirthStones
            = new string[] { "钻石", "蓝宝石", "玛瑙", "珍珠", "红宝石", "红条纹玛瑙", "蓝宝石", "猫眼石", "黄宝石", "土耳其玉", "紫水晶", "月长石，血石" };

        private static string[] solarTermName ={
            "小寒", "大寒", "立春", "雨水","惊蛰", "春分", "清明", "谷雨","立夏", "小满", "芒种", "夏至",
            "小暑", "大暑", "立秋", "处暑","白露", "秋分", "寒露", "霜降","立冬", "小雪", "大雪", "冬至"};

        // 计算节气
        public string SolarTermQingMing(int year)
        {
            int month = 4;
            for (int n = month * 2 - 1; n <= month * 2; n++)
            {
                double Termdays = Term(year, n, true);
                double mdays = AntiDayDifference(year, Math.Floor(Termdays));
                double sm1 = Math.Floor(mdays / 100);
                //int hour = (int)Math.Floor((double)Tail(Termdays) * 24);
                //int minute = (int)Math.Floor((double)(Tail(Termdays) * 24 - hour) * 60);
                int tMonth = (int)Math.Ceiling((double)n / 2);
                int day = (int)mdays % 100;

                if (solarTermName[n - 1] == "清明")
                    return new DateTime(year, tMonth, day).ToString("yyyy-MM-dd");
            }
            return null;
        }

        //返回y年第n个节气（如小寒为1）的日差天数值（pd取值真假，分别表示平气和定气）
        private double Term(int y, int n, bool pd)
        {
            //儒略日
            double juD = y * (365.2423112 - 6.4e-14 * (y - 100) * (y - 100) - 3.047e-8 * (y - 100)) + 15.218427 * n + 1721050.71301;

            //角度
            double tht = 3e-4 * y - 0.372781384 - 0.2617913325 * n;

            //年差实均数
            double yrD = (1.945 * Math.Sin(tht) - 0.01206 * Math.Sin(2 * tht)) * (1.048994 - 2.583e-5 * y);

            //朔差实均数
            double shuoD = -18e-4 * Math.Sin(2.313908653 * y - 0.439822951 - 3.0443 * n);

            double vs = (pd) ? (juD + yrD + shuoD - EquivalentStandardDay(y, 1, 0) - 1721425) : (juD - EquivalentStandardDay(y, 1, 0) - 1721425);
            return vs;
        }


        // 返回阳历y年日差天数为x时所对应的月日数（如y=2000，x=274时，返回1001(表示10月1日，即返回100*m+d)）
        private double AntiDayDifference(int y, double x)
        {
            int m = 1;
            for (int j = 1; j <= 12; j++)
            {
                int mL = DayDifference(y, j + 1, 1) - DayDifference(y, j, 1);
                if (x <= mL || j == 12)
                {
                    m = j;
                    break;
                }
                else
                    x -= mL;
            }
            return 100 * m + x;
        }


        // 返回x的小数尾数，若x为负值，则是1-小数尾数
        private double Tail(double x)
        {
            return x - Math.Floor(x);
        }
        // 返回等效标准天数（y年m月d日相应历种的1年1月1日的等效(即对Gregorian历与Julian历是统一的)天数）
        private double EquivalentStandardDay(int y, int m, int d)
        {
            //Julian的等效标准天数
            double v = (y - 1) * 365 + Math.Floor((double)((y - 1) / 4)) + DayDifference(y, m, d) - 2;

            if (y > 1582)
            {//Gregorian的等效标准天数
                v += -Math.Floor((double)((y - 1) / 100)) + Math.Floor((double)((y - 1) / 400)) + 2;
            }
            return v;
        }


        // 返回阳历y年m月d日的日差天数（在y年年内所走过的天数，如2000年3月1日为61）
        private int DayDifference(int y, int m, int d)
        {
            int ifG = IfGregorian(y, m, d, 1);
            int[] monL = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
            if (ifG == 1)
                if ((y % 100 != 0 && y % 4 == 0) || (y % 400 == 0))
                    monL[2] += 1;
                else
                    if (y % 4 == 0)
                        monL[2] += 1;
            int v = 0;
            for (int i = 0; i <= m - 1; i++)
            {
                v += monL[i];
            }
            v += d;
            if (y == 1582)
            {
                if (ifG == 1)
                    v -= 10;
                if (ifG == -1)
                    v = 0;  //infinity
            }
            return v;
        }
        // 判断y年m月(1,2,..,12,下同)d日是Gregorian历还是Julian历
        //（opt=1,2,3分别表示标准日历,Gregorge历和Julian历）,是则返回1，是Julian历则返回0，
        // 若是Gregorge历所删去的那10天则返回-1
        private int IfGregorian(int y, int m, int d, int opt)
        {
            if (opt == 1)
            {
                if (y > 1582 || (y == 1582 && m > 10) || (y == 1582 && m == 10 && d > 14))
                    return (1);     //Gregorian
                else
                    if (y == 1582 && m == 10 && d >= 5 && d <= 14)
                        return (-1);  //空
                    else
                        return (0);  //Julian
            }

            if (opt == 2)
                return (1);     //Gregorian
            if (opt == 3)
                return (0);     //Julian
            return (-1);
        }

        /// <summary>
        /// 获取某一年中的所有周末
        /// </summary>
        /// <param name="year"></param>
        /// <returns></returns>
        public static List<string> TheWenkEndOfYear(int year)
        {
            List<string> list = new List<string>();
            for (int i = 0; i < 12; i++)
            {
                int days = DateTime.DaysInMonth(year, (i + 1));
                for (int j = 0; j < days; j++)
                {
                    DateTime date = DateTime.Parse(year + "-" + (i + 1) + "-" + (j + 1));
                    if (date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday)
                    {
                        list.Add(date.ToString("yyyy-MM-dd"));
                    }
                }
            }
            return list;
        }
    }
}
